﻿using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using Pfz.Threading;
using Pfz;

namespace System.Net.Sockets
{
	/// <summary>
	/// Silverlight implementation of a synchronous TcpClient.
	/// </summary>
	public sealed class TcpClient:
		ThreadSafeDisposable
	{
		internal Socket _socket;
		private readonly SocketAsyncEventArgs _receiveArgs = new SocketAsyncEventArgs();
		private readonly ManagedAutoResetEvent _receiveEvent = new ManagedAutoResetEvent();
		private readonly SocketAsyncEventArgs _sendArgs = new SocketAsyncEventArgs();
		private readonly ManagedAutoResetEvent _sendEvent = new ManagedAutoResetEvent();
		
		/// <summary>
		/// Creates a new TcpClient instance connecting to the given host and port.
		/// </summary>
		public TcpClient(string host, int port)
		{
			ReceiveTimeout = -1;
			SendTimeout = -1;
			
			_socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
			
			var connectArgs = new SocketAsyncEventArgs();
			connectArgs.RemoteEndPoint = new DnsEndPoint(host, port);
			
			using(var mre = new ManualResetEvent(false))
			{
				connectArgs.Completed += (a, b) => mre.Set();
				
				if (_socket.ConnectAsync(connectArgs))
					mre.WaitOne();
			}
			
			_CheckResult(connectArgs.SocketError);
			
			_receiveArgs.Completed += _ReceiveCompleted;
			_sendArgs.Completed += _SendCompleted;
			_stream = new NetworkStream(this);
		}
		
		/// <summary>
		/// Releases the actual socket resources.
		/// </summary>
		protected override void Dispose(bool disposing)
		{
			if (disposing)
				Disposer.Dispose(ref _socket);

			base.Dispose(disposing);
		}
		
		private readonly NetworkStream _stream;
		/// <summary>
		/// Gets a NetworkStream used to read and write to this socket.
		/// </summary>
		/// <returns></returns>
		public NetworkStream GetStream()
		{
			return _stream;
		}
		
		private void _CheckResult(SocketError error)
		{
			if (WasDisposed)
				throw new ObjectDisposedException(GetType().FullName);
		
			if (error != SocketError.Success)
				throw new IOException(error.ToString());
		}
		
		private void _ReceiveCompleted(object sender, SocketAsyncEventArgs args)
		{
			_receiveEvent.Set();
		}
		/// <summary>
		/// Receives data from the socket and stores it in the given buffer.
		/// </summary>
		public int Receive(byte[] buffer, int offset, int count)
		{
			_receiveArgs.SetBuffer(buffer, offset, count);
			
			if (_socket.ReceiveAsync(_receiveArgs))
			{
				if (!_receiveEvent.WaitOne(ReceiveTimeout))
				{
					Dispose();
					throw new TimeoutException("Socket receive timed-out.");
				}
			}
				
			if (WasDisposed || _receiveArgs.SocketError != SocketError.Success)
				return 0;
				
			return _receiveArgs.BytesTransferred;
		}
		
		private void _SendCompleted(object sender, SocketAsyncEventArgs args)
		{
			_sendEvent.Set();
		}
		/// <summary>
		/// Tries to sends the given buffer.
		/// Return the number of bytes actually sent.
		/// </summary>
		public int Send(byte[] buffer, int offset, int count)
		{
			_sendArgs.SetBuffer(buffer, offset, count);
			
			if (_socket.SendAsync(_sendArgs))
			{
				if (!_sendEvent.WaitOne(SendTimeout))
				{
					Dispose();
					throw new TimeoutException("Socket send timed-out.");
				}
			}
			
			if (WasDisposed || _sendArgs.SocketError != SocketError.Success)
				return 0;
					
			return _sendArgs.BytesTransferred;
		}

		/// <summary>
		/// Sets the receive timeout.
		/// -1 means infinite, not zero.
		/// </summary>
		public int ReceiveTimeout { get; set; }

		/// <summary>
		/// Sets the send timeout.
		/// -1 means infinite, not zero.
		/// </summary>
		public int SendTimeout { get; set; }

		/// <summary>
		/// Gets a value indicating if this socket is connected.
		/// </summary>
		public bool Connected
		{
			get
			{
				var socket = _socket;
				if (socket == null)
					return false;

				return socket.Connected;
			}
		}
	}
}
